/*********************************************************************
*
* Software License Agreement (BSD License)
*
*  Copyright (c) 2010, Heriot-Watt University, UK.
*  All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the Heriot-Watt University nor the names of 
*     its contributors may be used to endorse or promote products 
*     derived from this software without specific prior written
*     permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*
*  Author: Joel Cartwright
*
*********************************************************************/

// Define DEBUG_OUTPUT to enable PRINTLN_DEBUG output when not using ROS.
// ROS debug level output is toggled at runtime using rxconsole.
//#define DEBUG_OUTPUT

// ================================================================== includes

#include <iostream>

using namespace std;

#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <osl_core/ProcessWatcher.h>

// ========================================================== static variables

namespace osl_core
{

bool ProcessWatcher::runOnce_ = false;

// Signal variable is volatile because signals can happen at any time.
volatile int ProcessWatcher::caughtSignal_ = 0;

// =================================================================== methods

// ---------------------------------------------------------------------------
void ProcessWatcher::init(int restartTimeoutMs)
{
	if (ProcessWatcher::runOnce_) {
		cerr << "ProcessWatcher: init() has already been called! Should only be called once per process!" << endl;
		abort();
	}

	ProcessWatcher::runOnce_ = true;

	bool exitWatcher = false;
	int childPID = 0;

	// Install signal handler.
	signal(SIGCHLD, &ProcessWatcher::signalHandler);
	signal(SIGTERM, &ProcessWatcher::signalHandler);
	signal(SIGINT,  &ProcessWatcher::signalHandler);

	cout << getpid() << " ProcessWatcher: Forking child process." << endl;

	while (!exitWatcher)
	{
		childPID = fork();
		switch (childPID)
		{
		case -1:
			cerr << "ProcessWatcher: Process creation error, PID: " << getpid() << endl;
			exit(-1);
			break;

		case 0:
			// =================================== Child Process : Taking care of the process body
			// Return signal handling to defaults for child (it will probably install its own).
			signal(SIGCHLD, SIG_DFL);
			signal(SIGTERM, SIG_DFL);
			signal(SIGINT,  SIG_DFL);

			// Now return to continue running the child.
			return;
			// Execution never reaches this point.
			break;

		default:
			break;
		}

		// =============================== Parent Process : Taking care of the process status
		cout << getpid() << " ProcessWatcher: Child PID " << childPID << endl;

		while (ProcessWatcher::caughtSignal_ || (!exitWatcher && childPID != 0))
		{
			// Sleep will be interrupted by signals.
			usleep(10000);

			// Check for signals.
			if (ProcessWatcher::caughtSignal_)
			{
				int thisSignal = ProcessWatcher::caughtSignal_;
				// Clear signal in case we catch another whilst handling this one.
				ProcessWatcher::caughtSignal_ = 0;
				switch(thisSignal)
				{
				case SIGCHLD:
					int childExitStatus;
					waitpid(childPID, &childExitStatus, 0);
					cout << "---------------------------------------------------------------------------" << endl
							<< getpid() << " ProcessWatcher: Caught SIGCHLD" << endl;
					// Zero child PID so we don't try to kill a non-existent child.
					childPID = 0;

					if (childExitStatus == 0) {
						cout << getpid() << " ProcessWatcher: Child exited cleanly with return 0; stopping now." << endl
							<< "---------------------------------------------------------------------------" << endl;
						exitWatcher = true;
					}
					else if (childExitStatus == SIGINT) {
						cout << getpid() << " ProcessWatcher: Child exited on signal INT (Ctrl-C); stopping now." << endl
							<< "---------------------------------------------------------------------------" << endl;
						exitWatcher = true;
					}
					else if (childExitStatus == SIGTERM) {
						cout << getpid() << " ProcessWatcher: Child exited on signal TERM; stopping now." << endl
							<< "---------------------------------------------------------------------------" << endl;
						exitWatcher = true;
					}
					else if (childExitStatus == SIGKILL) {
						cout << getpid() << " ProcessWatcher: Child exited on signal KILL; stopping now." << endl
							<< "---------------------------------------------------------------------------" << endl;
						exitWatcher = true;
					}
					else {
						cout << getpid() << " ProcessWatcher: Child exited with error return " << dec << childExitStatus << endl;
						cout << getpid() << " Restarting child after " <<  restartTimeoutMs << " ms delay" << endl
							<< "---------------------------------------------------------------------------" << endl;
						// Sleep until we are ready to restart the core - signals TERM and INT will interrupt this sleep.
						usleep(restartTimeoutMs * 1000);
					}
					break;

				case SIGTERM:
				case SIGINT:
					// Sleep for a short time to allow the child to exit first.
					usleep(10000);
					cout << "---------------------------------------------------------------------------" << endl
						<< getpid() << " ProcessWatcher: Caught signal " << thisSignal << "; exiting now." << endl
						<< "---------------------------------------------------------------------------" << endl;
					exitWatcher = true;
					break;

				default:
					cout << "---------------------------------------------------------------------------" << endl
						<< getpid() << " ProcessWatcher: Caught unexpected signal: " << thisSignal << endl
						<< "---------------------------------------------------------------------------" << endl;
					break;
				}
			}
		}
	} // while

	if (childPID != 0) {
		// Try to do a nice stop
		cout << getpid() << " ProcessWatcher: Sending term signal to child process" << endl;
		kill(childPID, SIGTERM);

		// Sleep for up to sleep delay milliseconds; will be less if receive SIGCHLD
		usleep(CHILD_KILL_SLEEP_USEC);

		if (ProcessWatcher::caughtSignal_ == SIGCHLD)
		{
			int childExitStatus;
			waitpid(childPID, &childExitStatus, 0);
			cout << getpid() << " ProcessWatcher: Child exited after kill with return " << childExitStatus << endl;
		}
		else {
			cout << getpid() << " ProcessWatcher: Sending kill signal to child process" << endl;
			kill(childPID, SIGKILL);
		}
	}

	exit(0);
}

// ---------------------------------------------------------------------------
void ProcessWatcher::signalHandler(int signal)
{
	ProcessWatcher::caughtSignal_ = signal;
}

} // namespace
